//
//  GSSObject.m
//  iGSS
//
//  Copyright 2010 Electronic Business Systems Ltd.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//

#import "GSSObject.h"

#import "NSString+GSSAdditions.h"
#import "FormattingUtilities.h"
#import "ImageUtilities.h"
#import "GSSCommunicator.h"
#import "MIME.h"

#include <sys/xattr.h>

#define META_STALENESS_INTERVAL 120

static NSString *GSSFetchDateKey = @"iGSS.fetchDate";

const char *GSSAcessDateAttr = "iGSS.accessDate";

@implementation GSSObject

@synthesize meta;
@synthesize children;

@synthesize uri;
@synthesize type;
@synthesize error;
@synthesize referer;

@synthesize uploading;
@synthesize childrenUpdated;


//MARK: -
//MARK: Instance Methods

- (id)initWithURI:(NSString *)aURI withType:(GSSObjectType)aType {
    if (!aURI)
        return nil;
    if (self = [super init]) {
        uri = [aURI copy];
        type = aType;
        meta = [[NSMutableDictionary alloc] init];
        children = [[NSMutableArray alloc] init];
        [self load];
    }
    return self;    
}

- (void)dealloc {
    [meta release];
    [children release];

    [uri release];
    
    [referer release];
    
    [super dealloc];
}

- (NSComparisonResult)compare:(GSSObject *)object {
    if (self.type == object.type)
        return [self.name caseInsensitiveCompare:object.name];
    else if (self.type == GSSObjectTypeFolder)
        return NSOrderedAscending;
    else
        return NSOrderedDescending;
}


//MARK: -
//MARK: Properties

- (NSString *)name {
    return [meta objectForKey:@"name"];
}

- (void)setName:(NSString *)newName {
    [meta setObject:newName forKey:@"name"];
}

- (unsigned long long)size {
    if (type == GSSObjectTypeFile)
        return [[meta objectForKey:@"size"] unsignedLongLongValue];
    else
        return [[meta valueForKeyPath:@"files.@sum.size"] unsignedLongLongValue];
}

- (void)setSize:(unsigned long long)newSize {
    [meta setObject:[NSNumber numberWithUnsignedLongLong:newSize] forKey:@"size"];
}

- (NSInteger)version {
    return [[meta objectForKey:@"version"] intValue];
}

- (void)setVersion:(NSInteger)newVersion {
    [meta setObject:[NSNumber numberWithInt:newVersion] forKey:@"version"];
}

- (BOOL)versioned {
    return [[meta objectForKey:@"versioned"] boolValue];
}

- (void)setVersioned:(BOOL)value {
    [meta setObject:[NSNumber numberWithBool:value] forKey:@"versioned"];
}

- (BOOL)readForAll {
    return [[meta objectForKey:@"readForAll"] boolValue];
}

- (void)setReadForAll:(BOOL)value {
    [meta setObject:[NSNumber numberWithBool:value] forKey:@"readForAll"];
}

- (BOOL)deleted {
    return [[meta objectForKey:@"deleted"] boolValue];
}

- (void)setDeleted:(BOOL)value {
    [meta setObject:[NSNumber numberWithBool:value] forKey:@"deleted"];
}

- (NSArray *)tags {
    return [meta objectForKey:@"tags"];
}

- (void)setTags:(NSArray *)newTags {
    [meta setObject:newTags forKey:@"tags"];
}

- (NSArray *)permissions {
    return [meta objectForKey:@"permissions"];
}

- (void)setPermissions:(NSArray *)newPermissions {
    [meta setObject:newPermissions forKey:@"permissions"];
}

- (NSString *)content {
    return [meta objectForKey:@"content"];
}

- (void)setContent:(NSString *)newContent {
    if (newContent)
        [meta setObject:newContent forKey:@"content"];
    else
        [meta removeObjectForKey:@"content"];
}

- (NSDate *)fetchDate {
    return [meta objectForKey:GSSFetchDateKey];
}

- (void)setFetchDate:(NSDate *)newDate {
    [meta setObject:newDate forKey:GSSFetchDateKey];
}


- (NSString *)path {
    NSString *path = [meta objectForKey:@"path"];
    if (!path) {
        GSSCommunicator *communicator = [GSSCommunicator defaultCommunicator];
        NSString *filesURI = communicator.home.rootURI;
        if ([uri hasPrefix:filesURI])
            path = [[[uri substringFromIndex:[filesURI length]]
                     stringByDeletingLastPathComponent] stringByAppendingSlash];
    }
    return path;
}

- (NSString *)owner {
    return [meta objectForKey:@"owner"];
}

- (NSString *)createdBy {
    return [meta objectForKey:@"createdBy"];
}

- (NSString *)modifiedBy {
    return [meta objectForKey:@"modifiedBy"];
}

- (NSDate *)creationDate {
    unsigned long long t = [[meta objectForKey:@"creationDate"] unsignedLongLongValue];
    if (!t)
        return nil;
    return [NSDate dateWithTimeIntervalSince1970:(t / 1000)];
}

- (NSDate *)modificationDate {
    unsigned long long t = [[meta objectForKey:@"modificationDate"] unsignedLongLongValue];
    if (!t)
        return nil;
    return [NSDate dateWithTimeIntervalSince1970:(t / 1000)];
}



- (BOOL)isEmpty {
    return [meta count] < 2;
}

- (BOOL)isPartial {
    // Use `versioned` to determine if a file is complete
	if (type == GSSObjectTypeFile)
        return [meta objectForKey:@"versioned"] ? NO : YES;
    else
        return [meta count] < 3;
}

- (BOOL)isStale {
    NSDate *fetchDate = self.fetchDate;
    if (!fetchDate)
        return YES;
    
    NSTimeInterval timeSinceNow = -[fetchDate timeIntervalSinceNow];
    return timeSinceNow > META_STALENESS_INTERVAL;
}

- (BOOL)isShared {
	if (self.readForAll)
		return YES;
    
	NSString *username = [[GSSCommunicator defaultCommunicator] username];
	for (NSDictionary *permission in [meta objectForKey:@"permissions"]) {
		NSString *user = [permission objectForKey:@"user"];
		BOOL read = [[permission objectForKey:@"read"] boolValue];
		if (read && ![username isEqual:user])
			return YES;
	}
    
	return NO;
}

- (BOOL)hasExpiredData {
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSDate *date = [[fileManager attributesOfItemAtPath:self.dataCachePath error:NULL]
                    fileModificationDate];
    return [date timeIntervalSinceDate:self.modificationDate] <= 0; 
}


- (NSData *)data {
    return [NSData dataWithContentsOfFile:self.dataCachePath];
}

- (UIImage *)icon {
	switch (self.type) {
        case GSSObjectTypeRoot:
            return [UIImage imageNamed:@"home.png"];
		case GSSObjectTypeFolder:
			return [UIImage imageNamed:@"folder.png"];
        case GSSObjectTypeShared:
            return [UIImage imageNamed:@"shared.png"];
        case GSSObjectTypeOthers:
            return [UIImage imageNamed:@"others.png"];
        case GSSObjectTypeTrash:
            return [UIImage imageNamed:@"trash.png"];
        case GSSObjectTypeUser:
            return [UIImage imageNamed:@"user.png"];
		default:
            if (self.isShared) {
                return [ImageUtilities imageWithImage:[MIME iconForType:self.content]
                                              overlay:[UIImage imageNamed:@"shared-emblem.png"]
                                                point:CGPointMake(16, 0)];
            } else {
                return [MIME iconForType:self.content];
            }
	}
}

- (GSSObject *)parent {
    NSString *parentURI = [uri stringByDeletingLastURIComponent];
    return [[GSSCommunicator defaultCommunicator] objectWithURI:parentURI
                                                       withType:GSSObjectTypeFolder];
}


- (NSString *)metaCachePath {
    NSString *filename = [uri SHA1Hash];
    return [[NSString stringWithFormat:@"%@/%@.plist", GSSMetaCachePath, filename]
            stringByExpandingTildeInPath];
}

- (NSString *)dataCachePath {
    NSString *unversionedURI = [uri stringByDeletingURIQuery];
    NSString *filename = [unversionedURI SHA1Hash];
	NSString *extension = [unversionedURI pathExtension];	
	return [[NSString stringWithFormat:@"%@/%@-%d.%@", GSSDataCachePath, filename, self.version, extension]
            stringByExpandingTildeInPath];
}


//MARK: -

- (void)updateMeta:(NSDictionary *)newMeta {
    if (!newMeta)
        return;
    
    [self willChangeValueForKey:@"meta"];
    [meta addEntriesFromDictionary:newMeta];
        
    NSArray *files = [newMeta objectForKey:@"files"];
    NSArray *folders = [newMeta objectForKey:@"folders"];
    
    if (files || folders) {
        [self willChangeValueForKey:@"children"];
        [children removeAllObjects];

        GSSCommunicator *communicator = [GSSCommunicator defaultCommunicator];
        
        for (NSDictionary *item in files) {
            NSString *itemURI = [item objectForKey:@"uri"];
            GSSObject *child = [communicator objectWithURI:itemURI withType:GSSObjectTypeFile];
            [child updateMeta:item];
            [children addObject:child];
        }
        
        for (NSDictionary *item in folders) {
            NSString *itemURI = [item objectForKey:@"uri"];
            GSSObject *child = [communicator objectWithURI:itemURI withType:GSSObjectTypeFolder];
            [child updateMeta:item];
            [children addObject:child];
        }
        
        [self didChangeValueForKey:@"children"];
    }
    
    [self didChangeValueForKey:@"meta"];
}

- (void)addChild:(GSSObject *)child {
    if ([[children valueForKey:@"name"] containsObject:child.name])
        return;
    [self willChangeValueForKey:@"children"];
    [children addObject:child];
    [self didChangeValueForKey:@"children"];
}

- (void)removeChild:(GSSObject *)child {
    if (![self.children containsObject:child])
        return;
    [self willChangeValueForKey:@"children"];
    [children removeObject:child];
    [self didChangeValueForKey:@"children"];
}

- (void)load {
    [self updateMeta:[NSDictionary dictionaryWithContentsOfFile:self.metaCachePath]];
}

- (void)save {
    [meta writeToFile:self.metaCachePath atomically:YES];
    [self updateMetaAccessDate];
}

- (void)saveData:(NSData *)someData {
    if ([someData length]) {
        [self willChangeValueForKey:@"data"];
        [someData writeToFile:self.dataCachePath atomically:YES];
        [self didChangeValueForKey:@"data"];
        [self updateDataAccessDate];
    }
}

- (void)delete {
    [meta removeAllObjects];
    [children removeAllObjects];
    [[NSFileManager defaultManager] removeItemAtPath:self.metaCachePath error:NULL];
    [[NSFileManager defaultManager] removeItemAtPath:self.dataCachePath error:NULL];
}

- (void)updateMetaAccessDate {
    NSTimeInterval timestamp = [[NSDate date] timeIntervalSinceReferenceDate];    
    setxattr([self.metaCachePath UTF8String], GSSAcessDateAttr, &timestamp, sizeof(timestamp), 0, 0);
}

- (void)updateDataAccessDate {
    NSTimeInterval timestamp = [[NSDate date] timeIntervalSinceReferenceDate];    
    setxattr([self.dataCachePath UTF8String], GSSAcessDateAttr, &timestamp, sizeof(timestamp), 0, 0);
}

- (NSString *)uriForVersion:(NSInteger)aVersion {
    NSString *baseURI = uri;
    NSRange range = [uri rangeOfString:@"?"];
    if (range.location != NSNotFound)
        baseURI = [uri substringToIndex:range.location];    
    
    return [NSString stringWithFormat:@"%@?version=%d", baseURI, aVersion];
}

@end
